#ifndef GAME_MODEL_H
#define GAME_MODEL_H

#include <vector>
#include <queue>

#include "common.h"

#include <player_profile.h>


enum EVENT_TYPE { POINT_WIN, AREA_WIN };	


struct Move {
	PlayerProfile * player;
	int x;
	int y;
	~Move () {}
};

struct Event {
	PlayerProfile * player, * old;
	void * data;
	size_t size;
	EVENT_TYPE type;
	~Event () {
		TRACE ("~Event. Doing nothing.");
	}
};

class EventReceiver {
public:
	virtual void push (Event * e) = 0;
};

class IGameModel {
protected:
	EventReceiver * controller;
public:
	IGameModel (EventReceiver * c) : controller (c) {}
	virtual bool makeMove (const Move * m) = 0;		
};

struct DotsGameCell {
	PlayerProfile * player;
	bool free;
};

class DotsGameModel : public IGameModel {
protected:
	int width;
	int height;
	DotsGameCell ** field;		

	bool inside (int x, int y) {
		return (x >= 0) && (x < width) &&
			(y >= 0) && (y < height);
	}		
	
	/**/
	void checkChains (int I, int J) {
		const int magic = 5;
		int i, j;
		byte ** D;
		D = new byte*[height + magic];
		for (i = 0; i < height + magic; i++) {
			D [i] = new byte[width + magic];
			for (j = 0; j < width + magic; j++)
				D [i][j] = 0;
		}

		for (i = 0; i < height; i++)
			for (j = 0; j < width; j++)
				if (field [i][j].free || field [i][j].player != field[I][J].player)
					D [i + 1][j + 1] = 0;
				else 
					D [i + 1][j + 1] = 1;
		
		//BFS from (maxX, maxY)
		Point p (height + 1, width + 1);
		std::queue <Point> q;
		q.push (p);
		while (!q.empty()) {
			p = q.front ();
			q.pop ();
			if (D[p.x][p.y] != 0)
				continue;
			D [p.x][p.y] = 2;
			if (p.x + 1 < height + magic && D[p.x + 1][p.y] == 0) {
				q.push(Point(p.x + 1, p.y));
			}
			if (p.y + 1 < width + magic && D[p.x][p.y + 1] == 0) {
				q.push(Point(p.x, p.y + 1));
			}
			if (p.x - 1 >= 0 && D[p.x - 1][p.y] == 0) {
				q.push(Point(p.x - 1, p.y));
			}
			if (p.y - 1 >= 0 && D[p.x][p.y - 1] == 0) {
				q.push(Point(p.x, p.y - 1));
			}
		}

		Point * dots = new Point [width*height];

		int resCount = 0;
		for (i = 0; i <= height; i++)
			for (j = 0; j <= width; j++)
				if (D[i][j] == 0) {
					dots[resCount].x = i - 1;
					dots[resCount].y = j - 1;
					resCount++;
				}

		TRACE ("Fount %d dots. Saved. Clearing...", resCount);

		for (i = 0; i < height + magic; i++) {
			delete[] D[i];
		}
		delete[] D;

		TRACE ("Cleared. Notifying controller.");

		//now we have all won dots in resCount

		for (i = 0; i < resCount; i++) {
			if (!field[dots[i].x][dots[i].y].free && 
				field[dots[i].x][dots[i].y].player != field[I][J].player) {
					Event * e = new Event;
					e->type = POINT_WIN;
					e->player = field[I][J].player;
					e->old = field[dots[i].x][dots[i].y].player;
					e->data = &dots[i];
					e->size = sizeof (dots[i]);
					TRACE ("Notifying about (%d,%d, player = %p, old = %p)", dots[i].x, dots[i].y, e->player, e->old);
					controller->push (e);
					TRACE ("deleting");
					delete e;
					TRACE ("Deleted");
				}
			field[dots[i].x][dots[i].y].free = false;
			field[dots[i].x][dots[i].y].player = field[I][J].player;
		}

		TRACE ("Done. Clearing dots list");

		delete [] dots;

		TRACE ("All chains proccessed");

	}
	/**/

public:
	DotsGameModel (EventReceiver * c, int _width = 20, int _height = 20) 
		: IGameModel (c), width (_width), height (_height)
	{
		field = new DotsGameCell* [height];
		for (int i = 0; i < height; i++) {
			field [i] = new DotsGameCell [width];
			for (int j = 0; j < width; j++) {
				field [i][j].player = NULL;
				field [i][j].free = true;
			}
		}
	}

	bool makeMove (const Move * m) {
		int x = m->x;
		int y = m->y;
		if (!inside (x, y))
			return false;
		if (!field [x][y].free) {
			return false;
		}
		field [x][y].free = false;
		field [x][y].player = m->player;
		checkChains (x, y);
	}

	DotsGameCell * getCell (int x, int y) {
		return & field [x][y];
	}

	~DotsGameModel () {
		int i;
		for (i = 0; i < height; i++)
			delete[] field[i];
		delete[] field;
	}

	int getWidth () {
		return width;
	}

	int getHeight () {
		return height;
	}

};

#endif /* !GAME_MODEL_H */